package SpikingNeuron;

import java.util.ArrayList;
import java.util.List;

import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import Generic.*;
import RealGenotype.RealGenotype;



public class SpikingNeuron extends EvolutionaryAlgorithm {

	private List<Double> target;
	static final String INPUT_STRING1 = "izzy-train1.dat";
	static final String INPUT_STRING2 = "izzy-train2.dat";
	static final String INPUT_STRING3 = "izzy-train3.dat";
	static final String INPUT_STRING4 = "izzy-train4.dat";

	static final double THRESHOLD = 0;
	static final double WINDOW_SIZE = 5;
	
	private boolean fitnessStatisticsAreValid;
	
	List<Double> averages;
	List<Double> standardDeviations;
	
	protected void initialize(int popSize, double crossoverRate, double mutationRate) {
		target = ReadFile.ReadTargetFromFile(INPUT_STRING1);				
		initializeEvolutionaryAlgorithm(popSize, crossoverRate, mutationRate);
	}
	
	protected double calculateFitness(Phenotype ptype) {
		
		if(!fitnessStatisticsAreValid)
		{
			averages = calculateAverages();
			standardDeviations = calculateStandardDeviations(averages);
			fitnessStatisticsAreValid = true;
		}
		
		List<Double> spikeTrain = ((SpikingNeuronPhenotype)ptype).getSpikeTrain();
		
		//Comparing spikeTrain and target
		List<Integer> spikeTimes = findSpikeTimes(spikeTrain);
		List<Integer> targetTimes = findSpikeTimes(target);
				
		double fitness1 =  1/(spikeTimeDistanceMetric(spikeTimes,targetTimes)+1);
		double fitness2 =  1/(spikeIntervalDistanceMetric(spikeTimes,targetTimes)+1);
		double fitness3 =  1/(waveformDistanceMetric(spikeTrain,target)+1);

		return fitness1;
	}
	
	protected void performCrossover(Genotype genotype, Genotype genotype2) {
		genotype.onePointCrossover(genotype2);
	}

	protected void performMutation(Genotype genotype) {
		genotype.mutate(mutationRate);
	}

	protected Genotype generateRandomGenotype() {
		Genotype gtype = new RealGenotype(5,.5);
		gtype.randomize();
		return gtype;
	}

	protected Phenotype developPhenotype(Genotype gtype) {
		fitnessStatisticsAreValid = false;
		return new SpikingNeuronPhenotype((RealGenotype)gtype,1000);
	}

	
	private List<Double> calculateStandardDeviations(List<Double> averages) {
		List<Double> ret = new ArrayList<Double>();
		for(int i = 0; i<5;i++)
		{
			ret.add(0.0);
		}
		for(Individual ind : children)
		{
			for(int i = 0; i<5;i++)
			{
				double value = ret.get(i);
				double number = ((RealGenotype)ind.getGenotype()).get(i);
				
				value += Math.pow((number-averages.get(i)),2)/(double)children.getSize();
				ret.set(i,value);
			}
		}
		for(int i = 0; i<5;i++)
		{
			ret.set(i, Math.sqrt(ret.get(i)));
		}
		return ret;
	}

	private List<Double> calculateAverages() {
		List<Double> ret = new ArrayList<Double>();
		for(int i = 0; i<5;i++)
		{
			ret.add(0.0);
		}
		for(Individual ind : children)
		{
			for(int i = 0; i<5;i++)
			{
				double value = ret.get(i);
				value += ((RealGenotype)ind.getGenotype()).get(i)/(double)children.getSize();
				ret.set(i,value);
			}
		}
		return ret;
	}

	private double waveformDistanceMetric(List<Double> spikeTrain, List<Double> target) {
		double sum = 0;
		double p=2;
		for (int i =0; i<=1000; i++)
		{
			sum+=Math.pow(Math.abs(spikeTrain.get(i)-target.get(i)),p);
		}
		return Math.pow(sum,1/p)/(double)(1001);
	}

	private double spikeIntervalDistanceMetric(List<Integer> spikeTimes,List<Integer> targetTimes) {
		double sum = 0.0;
		int spikes = Math.min(spikeTimes.size(),targetTimes.size());
		
		double M = spikes;
		double N = Math.max(spikeTimes.size(),targetTimes.size());
		
		double penalty = (N-M)*1000/(2*M);
		
		double p = 2;
		
		for(int i = 1; i< spikes; i++)
		{
			sum+=Math.pow(Math.abs(spikeTimes.get(i)-spikeTimes.get(i-1)-targetTimes.get(i)+targetTimes.get(i-1)),p);
		}
		
		if(spikes == 1)
			return penalty;
		
		return Math.pow(sum+Math.pow(penalty,4),1/p)/(double)(spikes-1);
	}

	private double spikeTimeDistanceMetric(List<Integer> spikeTimes,List<Integer> targetTimes) {
		double sum = 0.0;
		int spikes = Math.min(spikeTimes.size(),targetTimes.size());
		double M = spikes;
		double N = Math.max(spikeTimes.size(),targetTimes.size());
		
		double penalty = (N-M)*1000/(2*M);
		
		double p = 2;
		
		for(int i = 0; i< spikes; i++)
		{
			sum+=Math.pow(Math.abs(spikeTimes.get(i)-targetTimes.get(i)),p);
		}
		
		return Math.pow(sum+Math.pow(penalty,2),1/p)/(double)spikes;
	}

	private List<Integer> findSpikeTimes(List<Double> train) {
		int halfWindow = (int)(WINDOW_SIZE/2);
		List<Integer> spikeTimes=new ArrayList<Integer>();
		
		for(int i = halfWindow; i<train.size()-halfWindow;)
		{
			double max = train.get(i);
			for(int k = i-halfWindow;k<=i+halfWindow;k++)
			{
				if(train.get(k)>max)
					max = train.get(k);
			}
			double val = train.get(i);
			
			if(val>=max && val > THRESHOLD)
			{
				spikeTimes.add(i);
				i+=halfWindow;
				
			}
			i++;
		}
		return spikeTimes;
	}

	protected void performParentSelection() {
		adults.keepBestIndividuals((int)(populationSize*.95));
		//children = SelectionPolicy.tournamentSelection(adults, populationSize, 2, .7);
		children = SelectionPolicy.sigmaScaling(adults, populationSize);
	}

	protected void performReplacement() {
		adults.keepBestIndividuals((int)(populationSize*.02));
		children.addPopulation(adults);
		ReplacementProtocol.overProduction(children,populationSize);
	}

	public double getAdditionalStatistic(String string) {
		return 0.0;
	}

	//GUI
	public JPanel getBestIndividualPanel() {
		SpikingNeuronPhenotype snPhenotype = (SpikingNeuronPhenotype)adults.getBestIndividual().getPhenotype();
		
		XYSeries activation = new XYSeries("Activation");
		
		for(int i = 0; i<=1000; i++)
		{
			activation.add(i, snPhenotype.get(i));
		}
		
		XYSeriesCollection collection = new XYSeriesCollection();
		
		collection.addSeries(activation);

		NumberAxis standardAxis = new NumberAxis("Voltage");
		XYPlot xyPlot = new XYPlot(collection, null, standardAxis,new StandardXYItemRenderer());

		CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new NumberAxis("Timestep"));
		
		plot.add(xyPlot,1);

		JFreeChart chart = new JFreeChart("Train",JFreeChart.DEFAULT_TITLE_FONT,plot,true);

		ChartPanel chartPanel = new ChartPanel(chart);
		chartPanel.setPreferredSize(new java.awt.Dimension(200, 200));
		chartPanel.setMaximumSize(new java.awt.Dimension(200, 200));
		chartPanel.setMinimumSize(new java.awt.Dimension(200, 200));
		
		JPanel ret = new JPanel();
		ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
		ret.add(chartPanel);
		
		
		String parameters  = snPhenotype.getParameterString();
		ret.add(new JLabel(parameters));
		
		return ret;
	}
		
	public JPanel getTargetPanel() {
		
		XYSeries activation = new XYSeries("Activation");
		
		for(int i = 0; i<=1000; i++)
		{
			activation.add(i, target.get(i));
		}
		
		XYSeriesCollection collection = new XYSeriesCollection();
		
		collection.addSeries(activation);

		NumberAxis standardAxis = new NumberAxis("Voltage");
		XYPlot xyPlot = new XYPlot(collection, null, standardAxis,new StandardXYItemRenderer());

		CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new NumberAxis("Timestep"));
		
		plot.add(xyPlot,1);

		JFreeChart chart = new JFreeChart("Target",JFreeChart.DEFAULT_TITLE_FONT,plot,true);

		ChartPanel chartPanel = new ChartPanel(chart);
		chartPanel.setPreferredSize(new java.awt.Dimension(200, 200));
		
		JPanel ret = new JPanel();		
		ret.add(chartPanel);
	
		return ret;
	}
	
	
	//SpikingNeuron Program
	public static void main(String[] args) {
		additionalParameters = new ArrayList<String>();
		additionalParameterValues = new ArrayList<Double>();
		additionalStatisticNames = new ArrayList<String>();
		
		EAApplicationFrame2 frame = new EAApplicationFrame2(SpikingNeuron.class);
		frame.setVisible(true);
	}
}
